﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using AzureGames.Model;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AzureGames.Domain.Services;
using AzureGames.Data;
using AzureGames.Domain.Models;
using AzureGames.Common;

using AzureGames.Data.InMemory;
using AzureGames.Domain.Tests.Helper;
using System.Threading.Tasks;
using System.Threading;

namespace AzureGames.Domain.Tests
{
    [TestClass]
    public class NotificationTests
    {
        IBoardRepository _boardRepository;
        IGameDefinitionRepository _gameDefinitionRepository;
        IResourceRepository _resourceRepository;
        IUserRepository _userRepository;
        IPlayerRepository _playerRepository;

        [TestInitialize]
        public void SetupRepositories()
        {
            // create in memory repositories
            _boardRepository = new BoardRepository();
            _gameDefinitionRepository = new GameDefinitionRepository();
            _resourceRepository = new ResourceRepository();
            _userRepository = new UserRepository();
            _playerRepository = new PlayerRepository();
        }

        [TestMethod]
        public void MultiplePlayersSingleWebsiteTest()
        {
            var notificationRepository = new NotificationRepository();
            
            NotificationService.Instance.IntializeNotificationService(notificationRepository, _boardRepository);

            // post some message that will not get delivered as they are before anyone logs in
            // and are general messages
            List<Guid> notificationBefore = PostNotifications(notificationRepository, 30, 0, Guid.Empty.ToString(), "NotificationBefore:{0}");
            notificationBefore.AddRange(PostNotifications(notificationRepository, 20, 1, Guid.NewGuid().ToString(), "ToAnotherPlayer {0}"));

            List<TestUser> users = TestUser.GetSampleUsers(100);

            // first 20 start a session
            users.Take(20).ForEach(u => NotificationService.Instance.AddSession(Guid.Empty.ToString()));

            // what happens when there are no messages for the user
            //Assert.AreEqual(NotificationService.Instance.GetNotifications(users[0].SessionID, 10).Count(), 0);

            // user 4 logs in
            users[3].PlayerID = Guid.NewGuid().ToString();
            //NotificationService.Instance.AddSession(users[3].SessionID, users[3].PlayerID);

            // post 2 general messages
            PostNotifications(notificationRepository, 2, 0, Guid.Empty.ToString(), "GeneralNotification:{0}");
            // post 1 specific to user 3 and user 4 and user 5
            PostNotifications(notificationRepository, 1, 0, users[2].PlayerID, "GeneralNotification:{0}");
            PostNotifications(notificationRepository, 1, 0, users[3].PlayerID, "GeneralNotification:{0}");
            PostNotifications(notificationRepository, 1, 0, users[4].PlayerID, "GeneralNotification:{0}");

            List<NotificationMessage> temp = new List<NotificationMessage>();

            // should receive 2
            //temp.AddRange(service.GetNotifications(users[0].SessionID, 10));
            Assert.AreEqual(temp.Count, 2);

            // should recieve 2
            temp.Clear();
            //temp.AddRange(service.GetNotifications(users[2].SessionID, 10));
            //Assert.AreEqual(temp.Count, 2);

            //// should recieve 3 as they are logged in
            //temp.Clear();
            //temp.AddRange(service.GetNotifications(users[3].SessionID, 10));
            //Assert.AreEqual(temp.Count, 3);

            //// should now receive 0
            //temp.Clear();
            //temp.AddRange(service.GetNotifications(users[0].SessionID, 10));
            //Assert.AreEqual(temp.Count, 0);

            //temp.Clear();
            //temp.AddRange(service.GetNotifications(users[2].SessionID, 10));
            //Assert.AreEqual(temp.Count, 0);

            //temp.Clear();
            //temp.AddRange(service.GetNotifications(users[3].SessionID, 10));
            //Assert.AreEqual(temp.Count, 0);

            //// user 3 logs in
            //service.AddSession(users[2].SessionID, users[3].PlayerID);

            //// should recieve 1
            //temp.Clear();
            //temp.AddRange(service.GetNotifications(users[2].SessionID, 10));
            Assert.AreEqual(temp.Count, 1);
        }

        [TestMethod]
        public void MultiplePlayersMultipleWebsitesTest()
        {
            var notificationRepository = new NotificationRepository();

            //NotificationService service1 = NotificationService.Instance.IntializeNotificationService(notificationRepository);
            //NotificationService service2 = NotificationService.Instance.IntializeNotificationService(notificationRepository);

            //List<TestUser> users1 = TestUser.GetSampleUsers(100);
            //List<TestUser> users2 = TestUser.GetSampleUsers(100);

            //// half logged in and half anonymous to servers
            //users1.Take(50).ForEach(u => service1.AddSession(u.SessionID, u.PlayerID));
            //users1.Skip(50).ForEach(u => service1.AddSession(u.SessionID, Guid.Empty.ToString()));

            //users2.Take(50).ForEach(u => service2.AddSession(u.SessionID, u.PlayerID));
            //users2.Skip(50).ForEach(u => service2.AddSession(u.SessionID, Guid.Empty.ToString()));

            //// post messages into shared repository
            //PostNotifications(notificationRepository, 10, 0, Guid.Empty.ToString(), "GeneralNotification:{0}");
            //users1.ForEach(u => PostNotifications(notificationRepository, 1, 1, u.PlayerID, "Specific Message {0}."));
            //users2.ForEach(u => PostNotifications(notificationRepository, 2, 1, u.PlayerID, "Specific Message {0}."));

            //// check the messages are delivered 
            //List<NotificationMessage> temp = new List<NotificationMessage>();

            //// select the first 10 from service 1
            //foreach (var user in users1)
            //{
            //    temp.AddRange(service1.GetNotifications(user.SessionID, 10));
            //}

            //Assert.AreEqual(temp.Count, 1000);

            //temp.Clear();
            //// select all from service 2
            //foreach (var user in users2)
            //{
            //    temp.AddRange(service2.GetNotifications(user.SessionID, 100));
            //}

            //// (10*100) (anon) + (2*50) (1/2 logged in specific) 
            //Assert.AreEqual(temp.Count, 1100);

            //temp.Clear();
            //// select remainded from service 1
            //foreach (var user in users1)
            //{
            //    temp.AddRange(service1.GetNotifications(user.SessionID, 10));
            //}

            //Assert.AreEqual(temp.Count, 50);
        }

        /// <summary>
        /// Tests to simulate multiple threads to identify concurrency issues
        /// </summary>
        [TestMethod]
        public void ThreadsTest()
        {
            var notificationRepository = new NotificationRepository();

            CancellationTokenSource tokenSource = new CancellationTokenSource();

            // users base created
            List<TestUser> users1 = TestUser.GetSampleUsers(100);
            List<TestUser> users2 = TestUser.GetSampleUsers(100);

            Task<int> service1 = Task.Factory.StartNew(NotificationServiceAction(notificationRepository, users1, tokenSource.Token));
            Task<int> service2 = Task.Factory.StartNew(NotificationServiceAction(notificationRepository, users2, tokenSource.Token));

            // give some time to start up
            service2.Wait(500);

            PostNotifications(notificationRepository, 10, 0, Guid.Empty.ToString(), "GeneralNotification:{0}");
            users1.ForEach(u => PostNotifications(notificationRepository, 1, 1, u.PlayerID, "Specific Message {0}."));
            users2.ForEach(u => PostNotifications(notificationRepository, 5, 1, u.PlayerID, "Specific Message {0}."));

            PostNotifications(notificationRepository, 10, 0, Guid.Empty.ToString(), "GeneralNotification:{0}");

            service1.Wait(500);

            tokenSource.Cancel();

            service1.Wait();
            service2.Wait();

            // service1 (100 * 10) + (100 * 1) + (100 * 10)
            Assert.AreEqual(service1.Result, 2100);
            // service2 (100 * 10) + (100 * 5) + (100 * 10)
            Assert.AreEqual(service2.Result, 2500);
        }

        private Func<int> NotificationServiceAction(NotificationRepository notificationRepository, List<TestUser> myUsers, CancellationToken token)
        {
            return () =>
            {
                int totalMessagesReceived = 0;
                token.ThrowIfCancellationRequested();

                //NotificationService service = NotificationService.Instance.IntializeNotificationService(notificationRepository);

                //// login with all users
                //myUsers.ForEach(u => service.AddSession(u.SessionID, u.PlayerID));

                //while (!token.IsCancellationRequested)
                //{
                //    myUsers.ForEach(u => totalMessagesReceived += service.GetNotifications(u.SessionID, 10).ToArray().Length);
                //}

                return totalMessagesReceived;
            };
        }

        private List<Guid> PostNotifications(NotificationRepository notificationRepository, int count, int gameDefinitionID, string player, string message)
        {
            List<Guid> notifications = new List<Guid>();

            for (int i = 0; i < count; i++)
            {
                var notification = new Notification
                {
                    ID = Guid.NewGuid(),
                    PlayerID = player,
                    Message = string.Format(message, i),
                    State = Notification.STATE.Pending,
                    GameDefinitionID = gameDefinitionID
                };
                notifications.Add(notification.ID);
                notificationRepository.PostNotification(notification);
            }

            return notifications;
        }



    }
}
